
// I N C L U D E S ///////////////////////////////////////////////////////////

#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <bios.h>
#include <fcntl.h>
#include <memory.h>
#include <malloc.h>
#include <math.h>
#include <string.h>
#include <search.h>             // this one is needed for qsort()

// include all of our stuff

#include "black3.h"
#include "black4.h"
#include "black5.h"
#include "black6.h"
#include "black8.h"
#include "black9.h"
#include "black11.h"

// G L O B A L S //////////////////////////////////////////////////////////////

float  clip_near_z   = 100,        // the near or hither clipping plane
       clip_far_z    = 3000,       // the far or yon clipping plane
       screen_width  = 320,        // dimensions of the screen
       screen_heigth = 200;

int viewing_distance = 200;         // distance of projection plane from camera

point_3d view_point  = {0,0,0,1};  // position of camera

vector_3d light_source = {-0.913913,0.389759,-0.113369}; // position of point light source

float ambient_light = 6;          // ambient light level

dir_3d view_angle = {0,0,0};      // angle of camera

matrix_4x4 global_view;           // the global inverse wortld to camera
                                  // matrix

RGB_palette color_palette_3d;     // the color palette used for the 3D system

int num_objects;                  // number of objects in the world

object_ptr world_object_list[MAX_OBJECTS];  // the objects in the world

int num_polys_frame;                        // the number of polys in this frame

facet_ptr world_polys[MAX_POLYS_PER_FRAME]; // the visible polygons for this frame

facet world_poly_storage[MAX_POLYS_PER_FRAME];  // the storage for the visible
                                                // polygons is pre-allocated
                                                // so it doesn't need to be
                                                // allocated frame by frame

// look up tables

float sin_look[360+1],                          // SIN from 0 to 360
      cos_look[360+1];                          // COSINE from 0 to 360

// the clipping region, set it to default on start up

int poly_clip_min_x = POLY_CLIP_MIN_X,
    poly_clip_min_y = POLY_CLIP_MIN_Y,

    poly_clip_max_x = POLY_CLIP_MAX_X,
    poly_clip_max_y = POLY_CLIP_MAX_Y;


sprite textures;                        // this holds the textures

// F U N C T I O N S /////////////////////////////////////////////////////////

void Make_Grey_Palette(void)
{
	// this function generates 64 shades of grey and places them in the palette
	// at locations 16 to 96

	int index;

	RGB_color color;

	// generate 64 shades of grey

	for (index=0; index<64; index++)
	{
		// grey equals equal percentage of Red, Green and Blue
		color.red   = index;
		color.green = index;
		color.blue  = index;

		// write the color in the palette starting at location 16, so as not to
		// fry the EGA pallete
		Write_Color_Reg(index+16,(RGB_color_ptr)&color);

	} // end for index

} // end Make_Grey_Palette

////////////////////////////////////////////////////////////////////////////////

void Draw_Triangle_2D_Text(int x1,int y1,
                           int x2,int y2,
                           int x3,int y3,
                           unsigned char far *buffer,
                           int texture_index)
{
	// this function draws a textured triangle, but doesn't perform any clipping
	// and is very slow (mostly for demonstrative purposes)

	int x,y,           // looping variables
		x_index,       // integer texture coordinates
		y_index,
		bottom_1,      // distance from top to middle of triangle on y axis
		bottom_2;      // distance from middle to borttom triangle on y axis

	float dx_right,     // the dx/dy ratio of the right edge of line
		  dx_left,      // the dx/dy ratio of the left edge of line
		  xs,xe,        // the starting and ending points of the edges
		  height_left,  // the heights of the triangle
		  height_right;

	float a,b,c,d,det,          // texture mapping inverse matrix elements
		  ix,iy,jx,jy,          // texture vectors
		  delta_uy,delta_vy,    // pre-computed deltas of textures coordinates on y axis
		  u_start,v_start,      // the starting u,v coordinates on each line
		  u_curr,v_curr;        // the current u,v texture coordinates

	unsigned char far *dest_addr;   // final destination address of memory write

	unsigned char far *text;        // texture memory

	// assign text pointer to current texture map
	text = textures.frames[texture_index];

	// compute height of sub triangles
	height_left  = y2-y1;
	height_right = y3-y1;

	// compute distances from starting y vertex

	if (y2>y3)
	{
	   bottom_1 = y3;
	   bottom_2 = y2;
	}
	else
	{
	   bottom_1 = y2;
	   bottom_2 = y3;
	} // end else

	// compute edge deltas
	dx_left  = (float)(x2-x1)/height_left;
	dx_right = (float)(x3-x1)/height_right;

	// set starting points
	xs = (float)x1;
	xe = (float)x1+(float)0.5;

	// compute vector components for texture calculations
	ix = (float)(x3-x1);
	iy = (float)(y3-y1);

	jx = (float)(x2-x1);
	jy = (float)(y2-y1);

	// compute determinant
	det = (ix*jy - iy*jx);

	// compute inverse matrix
	a =  jy/det;
	b = -jx/det;
	c = -iy/det;
	d =  ix/det;

	// compute starting texture coordinates
	u_start = 0;
	v_start = 0;

	// compute delta texture coordinates for unit change in y
	delta_uy=(a*dx_left + b);
	delta_vy=(c*dx_left + d);

	// compute starting address in video memory
	dest_addr = buffer+(y1<<8)+(y1<<6);

	// draw the triangle
	for (y=y1; y<=bottom_1; y++,dest_addr+=320)
	{
		// start off working texture coordinates for current row

		u_curr = u_start;
		v_curr = v_start;

		// draw line

		for (x=(int)xs; x<=(int)xe; x++)
		{
			x_index = abs((int)(u_curr*63+0.5));
			y_index = abs((int)(v_curr*63+0.5));

			// printf("\nu=%d v=%d  ",x_index, y_index);

			dest_addr[x] = text[y_index*64 + x_index];

			// adjust x texture coordinates

			u_curr+=a;
			v_curr+=c;

		} // end for x

		// adjust starting point and ending point
		xs+=dx_left;
		xe+=dx_right;

		// adjust texture coordinates based on y change
		u_start += delta_uy;
		v_start += delta_vy;

	} // end for

	// now recompute slope of shorter edge to make it complete triangle
	if (y3>y2)
	{
	   // recompute left edge slope
	   dx_left = (float)(x3-x2)/(float)(y3-y2);
	}
	else
	{
	   // y2>y3, recompute right edge slope
	   dx_right = (float)(x2-x3)/(float)(y2-y3);
	} // end else

	// recompute texture space coordinate increments for y changes
	delta_uy=(a*dx_left + b);
	delta_vy=(c*dx_left + d);

	// draw remainder of triangle
	for (y--; y<=bottom_2; y++,dest_addr+=320)
	{
		// start off working texture coordinates for current row
		u_curr = u_start;
		v_curr = v_start;

		// draw line

		for (x=(int)xs; x<=(int)xe; x++)
		{
			// scale each texture coordinate by 64 since texrtures are 64x64
			x_index = (int)(u_curr*63);
			y_index = (int)(v_curr*63);

			// plot textel on screen
			dest_addr[x] = text[y_index*64 + x_index];

			// adjust x texture coordinates
			u_curr+=a;
			v_curr+=c;

		} // end for x

		// adjust starting point and ending point
		xs+=dx_left;
		xe+=dx_right;

		// adjust texture coordinates based on y change
		u_start += delta_uy;
		v_start += delta_vy;

	} // end for

} // end Draw_Triangle_2D_Text

//////////////////////////////////////////////////////////////////////////////

void Draw_Triangle_2D_Gouraud(int x1,int y1,
                              int x2,int y2,
                              int x3,int y3,
                              unsigned char far *buffer,
                              int intensity_1,
                              int intensity_2,
                              int intensity_3)
{
	// this function draws a Gouraud shaded triangle, but doesn't perform any clipping
	// and is very slow (mostly for demonstrative purposes)

	int x,y,           // looping variables
		x_index,       // integer texture coordinates
		y_index,
		bottom_1,      // distance from top to middle of triangle on y axis
		bottom_2;      // distance from middle to borttom triangle on y axis

	float dx_right,     // the dx/dy ratio of the right edge of line
		  dx_left,      // the dx/dy ratio of the left edge of line
		  xs,xe,        // the starting and ending points of the edges
		  height_left,  // the heights of the triangle
		  height_right;

	float intensity_right,   // the intensity of the right edge of the triangle
		  intensity_left,    // the intensity of the left edge of the triangle
		  intensity_mid,     // the average between the right and left
		  delta_y21,delta_y31;  // the y delta's

	unsigned char far *dest_addr; // pointer to memory space of video write

	// compute height of sub triangles
	height_left  = y2-y1;
	height_right = y3-y1;

	// compute distances from starting y vertex
	if (y2>y3)
	{
	   bottom_1 = y3;
	   bottom_2 = y2;
	}
	else
	{
	   bottom_1 = y2;
	   bottom_2 = y3;
	} // end else

	// compute edge deltas
	dx_left  = (float)(x2-x1)/height_left;
	dx_right = (float)(x3-x1)/height_right;

	// set starting points
	xs = (float)x1;
	xe = (float)x1+(float)0.5;

	// compute shading constants
	delta_y21 = (float)1/(float)(y2-y1);
	delta_y31 = (float)1/(float)(y3-y1);

	// compute starting address in video memory
	dest_addr = buffer+(y1<<8)+(y1<<6);

	// draw the triangle using Gouraud shading
	for (y=y1; y<=bottom_1; y++,dest_addr+=320)
	{
		// compute left and right edge intensities as a function of y
		intensity_left  = delta_y21*(float)((y2-y)*intensity_1 + (y-y1)*intensity_2);
		intensity_right = delta_y31*(float)((y3-y)*intensity_1 + (y-y1)*intensity_3);

		// draw line
		for (x=(int)xs; x<=(int)xe; x++)
		{
			// compute x axis intensity interpolant
			intensity_mid = ((xe-x)*intensity_left + (x-xs)*intensity_right)/(xe-xs);

			// plot pixel on screen
			dest_addr[x] = (unsigned char)(16+intensity_mid);
		} // end for x

		// adjust starting point and ending point
		xs+=dx_left;
		xe+=dx_right;
	} // end for

	// now recompute slope of shorter edge to make it complete triangle

	if (y3>y2)
	{
		// recompute left edge slope
		dx_left = (float)(x3-x2)/(float)(y3-y2);

	}
	else
	{
		// y2>y3, recompute right edge slope
		dx_right = (float)(x2-x3)/(float)(y2-y3);
	} // end else

	// draw remainder of triangle
	for (y--; y<=bottom_2; y++,dest_addr+=320)
    {
		// compute left and right edge intensities as a function of y
		intensity_left  = (float)((y3-y)*intensity_2 + (y-y2)*intensity_3)/(float)(y3-y2);
		intensity_right = delta_y31*(float)((y3-y)*intensity_1 + (y-y1)*intensity_3);

		// draw line
		for (x=(int)xs; x<=(int)xe; x++)
        {
			// compute x axis intensity interpolant
			intensity_mid = ((xe-x)*intensity_left + (x-xs)*intensity_right)/(xe-xs);

			// plot pixel on screen
			dest_addr[x] = (unsigned char)(16+intensity_mid);
        } // end for x

		// adjust starting point and ending point
		xs+=dx_left;
		xe+=dx_right;
	} // end for

} // end Draw_Triangle_2D_Gouraud

////////////////////////////////////////////////////////////////////////////////

void Triangle_Line(unsigned char far *dest_addr,
                   unsigned int xs,
                   unsigned int xe,
                   int color)
{

	// this function draws a fast horizontal line by using WORD size writes
	// it's speed can be doubled by use of an external 32 bit DWORD version in
	// assembly...

	// plot pixels at ends of line first
	if (xs & 0x0001)
	{
		// plot a single starting pixel and move xs to an even boundary
		dest_addr[xs++] = (unsigned char)color;
	} // end if

	if (!(xe & 0x0001))
	{
		// plot a single terminating pixel and move xe back to an odd boundary
		dest_addr[xe--] = (unsigned char)color;
	} // end if

	// now plot the line
	dest_addr+=xs;

	// now blast the middle part of the line a WORD at a time, later use
	// an external assembly program to do it a DOUBLE WORD at a time!
	_asm
	{
		les di,dest_addr      ; point es:di at data area

		mov al,BYTE PTR color ; move into al and ah the color
		mov ah,al

		mov cx,xe             ; compute number of words to move  (xe-xs+1)/2
		sub cx,xs
		inc cx
		shr cx,1              ; divide by 2

		rep stosw             ; draw the line
	} // end asm

} // end Triangle_Line

//////////////////////////////////////////////////////////////////////////////

void Draw_Top_Triangle(int x1,int y1, int x2,int y2, int x3,int y3,int color)
{
	// this function draws a triangle that has a flat top
	float dx_right,    // the dx/dy ratio of the right edge of line
		  dx_left,     // the dx/dy ratio of the left edge of line
		  xs,xe,       // the starting and ending points of the edges
		  height;      // the height of the triangle

	int temp_x,        // used during sorting as temps
		temp_y,
		right,         // used by clipping
		left;

	unsigned char far *dest_addr;

	// test order of x1 and x2
	if (x2 < x1)
	{
	   temp_x = x2;
	   x2     = x1;
	   x1     = temp_x;
	} // end if swap

	// compute delta's
	height = y3-y1;

	dx_left  = (x3-x1)/height;
	dx_right = (x3-x2)/height;

	// set starting points
	xs = (float)x1;
	xe = (float)x2+(float)0.5;

	// perform y clipping
	if (y1<poly_clip_min_y)
	{
		// compute new xs and ys
		xs = xs+dx_left*(float)(-y1+poly_clip_min_y);
		xe = xe+dx_right*(float)(-y1+poly_clip_min_y);

		// reset y1
		y1=poly_clip_min_y;
	} // end if top is off screen

	if (y3>poly_clip_max_y)
		y3=poly_clip_max_y;

	// compute starting address in video memory
	dest_addr = double_buffer+(y1<<8)+(y1<<6);

	// test if x clipping is needed

	if (x1>=poly_clip_min_x && x1<=poly_clip_max_x &&
		x2>=poly_clip_min_x && x2<=poly_clip_max_x &&
		x3>=poly_clip_min_x && x3<=poly_clip_max_x)
    {
		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=320)
        {
			Triangle_Line(dest_addr,(unsigned int)xs,(unsigned int)xe,color);
			//_fmemset((unsigned char far *)dest_addr+(unsigned int)xs,
			//        color,
			//        (unsigned int)(xe-xs));

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;
        } // end for

	} // end if no x clipping needed
	else
	{
		// clip x axis with slower version
		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=320)
		{
			// do x clip
			left  = (int)xs;
			right = (int)xe;

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

			// clip line
			if (left < poly_clip_min_x)
			{
				left = poly_clip_min_x;

				if (right < poly_clip_min_x)
					continue;
			}

			if (right > poly_clip_max_x)
			{
				right = poly_clip_max_x;

				if (left > poly_clip_max_x)
					continue;
			}

			Triangle_Line(dest_addr,(unsigned int)left,(unsigned int)right,color);

			//_fmemset((unsigned char far *)dest_addr+(unsigned int)left,
			//        color,
			//        (unsigned int)(right-left));

		} // end for

	} // end else x clipping needed

} // end Draw_Top_Triangle

/////////////////////////////////////////////////////////////////////////////

void Draw_Bottom_Triangle(int x1,int y1, int x2,int y2, int x3,int y3,int color)
{
	// this function draws a triangle that has a flat bottom

	float dx_right,    // the dx/dy ratio of the right edge of line
		  dx_left,     // the dx/dy ratio of the left edge of line
		  xs,xe,       // the starting and ending points of the edges
		  height;      // the height of the triangle

	int temp_x,        // used during sorting as temps
		temp_y,
		right,         // used by clipping
		left;

	unsigned char far *dest_addr;

	// test order of x1 and x2
	if (x3 < x2)
	{
		temp_x = x2;
		x2     = x3;
		x3     = temp_x;
	} // end if swap

	// compute delta's
	height = y3-y1;

	dx_left  = (x2-x1)/height;
	dx_right = (x3-x1)/height;

	// set starting points
	xs = (float)x1;
	xe = (float)x1+(float)0.5;

	// perform y clipping
	if (y1<poly_clip_min_y)
	{
		// compute new xs and ys
		xs = xs+dx_left*(float)(-y1+poly_clip_min_y);
		xe = xe+dx_right*(float)(-y1+poly_clip_min_y);

		// reset y1
		y1=poly_clip_min_y;

	} // end if top is off screen

	if (y3>poly_clip_max_y)
		y3=poly_clip_max_y;

	// compute starting address in video memory
	dest_addr = double_buffer+(y1<<8)+(y1<<6);

	// test if x clipping is needed
	if (x1>=poly_clip_min_x && x1<=poly_clip_max_x &&
		x2>=poly_clip_min_x && x2<=poly_clip_max_x &&
		x3>=poly_clip_min_x && x3<=poly_clip_max_x)
    {
		// draw the triangle

		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=320)
		{
			Triangle_Line(dest_addr,(unsigned int)xs,(unsigned int)xe,color);

			//_fmemset((unsigned char far *)dest_addr+(unsigned int)xs,
			//        color,
			//        (unsigned int)(xe-xs));

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

        } // end for

    } // end if no x clipping needed
	else
	{
		// clip x axis with slower version
		// draw the triangle
		for (temp_y=y1; temp_y<=y3; temp_y++,dest_addr+=320)
		{
			// do x clip
			left  = (int)xs;
			right = (int)xe;

			// adjust starting point and ending point
			xs+=dx_left;
			xe+=dx_right;

			// clip line
			if (left < poly_clip_min_x)
			{
				left = poly_clip_min_x;

				if (right < poly_clip_min_x)
					continue;
			}

			if (right > poly_clip_max_x)
			{
				right = poly_clip_max_x;

				if (left > poly_clip_max_x)
				continue;
			}

			Triangle_Line(dest_addr,(unsigned int)left,(unsigned int)right,color);

		   //_fmemset((unsigned char far *)dest_addr+(unsigned int)left,
		   //        color,
		   //       (unsigned int)(right-left));

		} // end for

	} // end else x clipping needed

} // end Draw_Bottom_Triangle

///////////////////////////////////////////////////////////////////////////////

void Draw_Triangle_2D(int x1,int y1,
                      int x2,int y2,
                      int x3,int y3,int color)
{
	int temp_x,
		temp_y,
		new_x;

	// test for h lines and v lines
	if ((x1==x2 && x2==x3)  ||  (y1==y2 && y2==y3))
		return;

	// sort p1,p2,p3 in ascending y order
	if (y2<y1)
	{
	   temp_x = x2;
	   temp_y = y2;
	   x2     = x1;
	   y2     = y1;
	   x1     = temp_x;
	   y1     = temp_y;
	} // end if

	// now we know that p1 and p2 are in order
	if (y3<y1)
	{
		temp_x = x3;
		temp_y = y3;
		x3     = x1;
		y3     = y1;
		x1     = temp_x;
		y1     = temp_y;
	} // end if

	// finally test y3 against y2
	if (y3<y2)
	{
		temp_x = x3;
		temp_y = y3;
		x3     = x2;
		y3     = y2;
		x2     = temp_x;
		y2     = temp_y;
	} // end if

	// do trivial rejection tests
	if ( y3<poly_clip_min_y || y1>poly_clip_max_y ||
		(x1<poly_clip_min_x && x2<poly_clip_min_x && x3<poly_clip_min_x) ||
		(x1>poly_clip_max_x && x2>poly_clip_max_x && x3>poly_clip_max_x) )
		return;

	// test if top of triangle is flat
	if (y1==y2)
	{
		Draw_Top_Triangle(x1,y1,x2,y2,x3,y3,color);
	} // end if
	else if (y2==y3)
	{
		Draw_Bottom_Triangle(x1,y1,x2,y2,x3,y3,color);
	} // end if bottom is flat
	else
	{
		// general triangle that's needs to be broken up along long edge
		new_x = x1 + (int)((float)(y2-y1)*(float)(x3-x1)/(float)(y3-y1));

		// draw each sub-triangle
		Draw_Bottom_Triangle(x1,y1,new_x,y2,x2,y2,color);
		Draw_Top_Triangle(x2,y2,new_x,y2,x3,y3,color);
	} // end else

} // end Draw_Triangle_2D

////////////////////////////////////////////////////////////////////////////////

int Clip_Line(int *x1,int *y1,int *x2, int *y2)
{
	// this function clips the sent line using the globally defined clipping
	// region
	int point_1 = 0, point_2 = 0;  // tracks if each end point is visible or invisible
	int clip_always = 0;           // used for clipping override
	int xi,yi;                     // point of intersection

	int right_edge=0,              // which edges are the endpoints beyond
		left_edge=0,
		top_edge=0,
		bottom_edge=0;

	int success = 0;               // was there a successfull clipping
	float dx,dy;                   // used to holds slope deltas

	// test if line is completely visible
	if ( (*x1>=poly_clip_min_x) && (*x1<=poly_clip_max_x) &&
		 (*y1>=poly_clip_min_y) && (*y1<=poly_clip_max_y) )
		point_1 = 1;

	if ( (*x2>=poly_clip_min_x) && (*x2<=poly_clip_max_x) &&
		 (*y2>=poly_clip_min_y) && (*y2<=poly_clip_max_y) )
		point_2 = 1;

	// test endpoints
	if (point_1==1 && point_2==1)
		return(1);

	// test if line is completely invisible
	if (point_1==0 && point_2==0)
	{
		// must test to see if each endpoint is on the same side of one of
		// the bounding planes created by each clipping region boundary
		if ( ((*x1<poly_clip_min_x) && (*x2<poly_clip_min_x)) || // to the left
			 ((*x1>poly_clip_max_x) && (*x2>poly_clip_max_x)) || // to the right
			 ((*y1<poly_clip_min_y) && (*y2<poly_clip_min_y)) || // above
			 ((*y1>poly_clip_max_y) && (*y2>poly_clip_max_y)) )  // below
		{
			// no need to draw line
			return(0);
		} // end if invisible

		// if we got here we have the special case where the line cuts into and
		// out of the clipping region
		clip_always = 1;

	} // end if test for invisibly

	// take care of case where either endpoint is in clipping region
	if (( point_1==1) || (point_1==0 && point_2==0) )
	{
		// compute deltas
		dx = *x2 - *x1;
		dy = *y2 - *y1;

		// compute what boundary line need to be clipped against
		if (*x2 > poly_clip_max_x)
		{
			// flag right edge
			right_edge = 1;

			// compute intersection with right edge
			if (dx!=0)
				yi = (int)(.5 + (dy/dx) * (poly_clip_max_x - *x1) + *y1);
			else
				yi = -1;  // invalidate intersection

		} // end if to right
		else
		if (*x2 < poly_clip_min_x)
		{
			// flag left edge
			left_edge = 1;

			// compute intersection with left edge
			if (dx!=0)
				yi = (int)(.5 + (dy/dx) * (poly_clip_min_x - *x1) + *y1);
			else
				yi = -1;  // invalidate intersection
		} // end if to left

		// horizontal intersections
		if (*y2 > poly_clip_max_y)
		{
			// flag bottom edge
			bottom_edge = 1;

			// compute intersection with right edge
			if (dy!=0)
				xi = (int)(.5 + (dx/dy) * (poly_clip_max_y - *y1) + *x1);
			else
				xi = -1;  // invalidate inntersection
		} // end if bottom
		else
		if (*y2 < poly_clip_min_y)
		{
			// flag top edge
			top_edge = 1;

			// compute intersection with top edge
			if (dy!=0)
				xi = (int)(.5 + (dx/dy) * (poly_clip_min_y - *y1) + *x1);
			else
				xi = -1;  // invalidate intersection

		} // end if top

		// now we know where the line passed thru
		// compute which edge is the proper intersection

		if (right_edge==1 && (yi>=poly_clip_min_y && yi<=poly_clip_max_y) )
		{
			*x2 = poly_clip_max_x;
			*y2 = yi;

			success = 1;
		} // end if intersected right edge
		else
		if (left_edge==1 && (yi>=poly_clip_min_y && yi<=poly_clip_max_y) )
		{
			*x2 = poly_clip_min_x;
			*y2 = yi;

			success = 1;
		} // end if intersected left edge

		if (bottom_edge==1 && (xi>=poly_clip_min_x && xi<=poly_clip_max_x) )
		{
			*x2 = xi;
			*y2 = poly_clip_max_y;

			success = 1;
		} // end if intersected bottom edge
		else
		if (top_edge==1 && (xi>=poly_clip_min_x && xi<=poly_clip_max_x) )
		{
			*x2 = xi;
			*y2 = poly_clip_min_y;

			success = 1;
		} // end if intersected top edge

	} // end if point_1 is visible

	// reset edge flags
	right_edge = left_edge= top_edge = bottom_edge = 0;

	// test second endpoint

	if ( (point_2==1) || (point_1==0 && point_2==0))
	{
		// compute deltas
		dx = *x1 - *x2;
		dy = *y1 - *y2;

		// compute what boundary line need to be clipped against
		if (*x1 > poly_clip_max_x)
		{
			// flag right edge
			right_edge = 1;

			// compute intersection with right edge
			if (dx!=0)
				yi = (int)(.5 + (dy/dx) * (poly_clip_max_x - *x2) + *y2);
			else
				yi = -1;  // invalidate inntersection
		} // end if to right
		else
		if (*x1 < poly_clip_min_x)
		{
			// flag left edge
			left_edge = 1;

			// compute intersection with left edge
			if (dx!=0)
				yi = (int)(.5 + (dy/dx) * (poly_clip_min_x - *x2) + *y2);
			else
				yi = -1;  // invalidate intersection
		} // end if to left

		// horizontal intersections
		if (*y1 > poly_clip_max_y)
		{
			// flag bottom edge
			bottom_edge = 1;

			// compute intersection with right edge
			if (dy!=0)
				xi = (int)(.5 + (dx/dy) * (poly_clip_max_y - *y2) + *x2);
			else
				xi = -1;  // invalidate inntersection
		} // end if bottom
		else
		if (*y1 < poly_clip_min_y)
		{
			// flag top edge
			top_edge = 1;

			// compute intersection with top edge
			if (dy!=0)
				xi = (int)(.5 + (dx/dy) * (poly_clip_min_y - *y2) + *x2);
			else
				xi = -1;  // invalidate inntersection
		} // end if top

		// now we know where the line passed thru
		// compute which edge is the proper intersection

		if (right_edge==1 && (yi>=poly_clip_min_y && yi<=poly_clip_max_y) )
		{
			*x1 = poly_clip_max_x;
			*y1 = yi;

			success = 1;

		} // end if intersected right edge
		else
		if (left_edge==1 && (yi>=poly_clip_min_y && yi<=poly_clip_max_y) )
		{
			*x1 = poly_clip_min_x;
			*y1 = yi;

			success = 1;
		} // end if intersected left edge

		if (bottom_edge==1 && (xi>=poly_clip_min_x && xi<=poly_clip_max_x) )
		{
			*x1 = xi;
			*y1 = poly_clip_max_y;

			success = 1;
		} // end if intersected bottom edge
		else
		if (top_edge==1 && (xi>=poly_clip_min_x && xi<=poly_clip_max_x) )
		{
			*x1 = xi;
			*y1 = poly_clip_min_y;

			success = 1;
		} // end if intersected top edge

	} // end if point_2 is visible

	return(success);

} // end Clip_Line

//////////////////////////////////////////////////////////////////////////////

void Build_Look_Up_Tables(void)
{
	// this function builds all the look up tables for the engine
	int angle;
	float rad;

	// generate sin/cos look up tables
	for (angle=0; angle<=360; angle++)
	{
		rad = (float) (3.14159*(float)angle/(float)180);

		cos_look[angle] = (float)cos(rad);
		sin_look[angle] = (float)sin(rad);
    }
} // end Build_Look_Up_Tables

//////////////////////////////////////////////////////////////////////////////

float Dot_Product_3D(vector_3d_ptr u,vector_3d_ptr v)
{
	// this function computes the dot product of two vectors
	return( (u->x * v->x) + (u->y * v->y) + (u->z * v->z));
} // end Dot_Product

//////////////////////////////////////////////////////////////////////////////

void Make_Vector_3D(point_3d_ptr init,
                    point_3d_ptr term,
                    vector_3d_ptr result)
{
	// this function creates a vector from two points in 3D space
	result->x = term->x - init->x;
	result->y = term->y - init->y;
	result->z = term->z - init->z;
} // end Make_Vector

//////////////////////////////////////////////////////////////////////////////

void Cross_Product_3D(vector_3d_ptr u,
                      vector_3d_ptr v,
                      vector_3d_ptr normal)
{
	// this function computes the cross product between two vectors

	normal->x =  (u->y*v->z - u->z*v->y);
	normal->y = -(u->x*v->z - u->z*v->x);
	normal->z =  (u->x*v->y - u->y*v->x);

} // end Cross_Product_3D

///////////////////////////////////////////////////////////////////////////////

float Vector_Mag_3D(vector_3d_ptr v)
{
	// computes the magnitude of a vector
	return((float)sqrt(v->x*v->x + v->y*v->y + v->z*v->z));

} // end Vector_Mag_3D

///////////////////////////////////////////////////////////////////////////////

void Mat_Print_4x4(matrix_4x4 a)
{
	// this function prints out a 4x4 matrix

	int row,      // looping variables
		column;

	for (row=0; row<4; row++)
	{
		printf("\n");

		for (column=0; column<4; column++)
			printf("%f ",a[row][column]);
    }

	printf("\n");

} // end Mat_Print_4x4

//////////////////////////////////////////////////////////////////////////////

void Mat_Print_1x4(matrix_1x4 a)
{
	// this function prints out a 1x4 matrix
	int column;     // looping variable

	printf("\n");

	for (column=0; column<4; column++)
		printf("%f ",a[column]);

	printf("\n");

} // end Mat_Print_1x4

//////////////////////////////////////////////////////////////////////////////

void Mat_Mul_4x4_4x4(matrix_4x4 a,
                     matrix_4x4 b,
                     matrix_4x4 result)
{
	// this function multiplies a 4x4 by a 4x4 and stores the result in a 4x4

	int index_i,  // row and column looping vars
		index_j,
		index_k;

	float sum;    // temp used to hold sum of products

	// loop thru rows of a

	for (index_i=0; index_i<4; index_i++)
	{
		// loop thru columns of b
		for (index_j=0; index_j<4; index_j++)
		{
			// multiply ith row of a by jth column of b and store the sum
			// of products in the position i,j of result

			sum=0;

			for (index_k=0; index_k<4; index_k++)
				sum+=a[index_i][index_k]*b[index_k][index_j];

			// store result

			result[index_i][index_j] = sum;

		} // end for index_j

	} // end for index_i

} // end Mat_Mul_4x4_4x4

//////////////////////////////////////////////////////////////////////////////

void Mat_Mul_1x4_4x4(matrix_1x4 a,
                     matrix_4x4 b,
                     matrix_1x4 result)
{
	// this function multiplies a 1x4 by a 4x4 and stores the result in a 1x4

	int index_j,    // column index
		index_k;    // row index

	float sum;    // temp used to hold sum of products

	// loop thru columns of b
	for (index_j=0; index_j<4; index_j++)
	{
		// multiply ith row of a by jth column of b and store the sum
		// of products in the position i,j of result
		sum=0;

		for (index_k=0; index_k<4; index_k++)
			sum+=a[index_k]*b[index_k][index_j];

		// store result
		result[index_j] = sum;

    } // end for index_j

} // end Mat_Mul_1x4_4x4

//////////////////////////////////////////////////////////////////////////////

void Mat_Identity_4x4(matrix_4x4 a)
{
	// this function creates a 4x4 identity matrix

	a[0][1] = a[0][2] = a[0][3] = 0;
	a[1][0] = a[1][2] = a[1][3] = 0;
	a[2][0] = a[2][1] = a[2][3] = 0;
	a[3][0] = a[3][1] = a[3][2] = 0;

	// set main diagonal to 1's

	a[0][0] = a[1][1] = a[2][2] = a[3][3] = 1;

} // end Mat_Identity_4x4

/////////////////////////////////////////////////////////////////////////////

void Mat_Zero_4x4(matrix_4x4 a)
{
	// this function zero's out a 4x4 matrix

	a[0][0] = a[0][1] = a[0][2] = a[0][3] = 0;
	a[1][0] = a[1][1] = a[1][2] = a[1][3] = 0;
	a[2][0] = a[2][1] = a[2][2] = a[2][3] = 0;
	a[3][0] = a[3][1] = a[3][2] = a[3][3] = 0;

} // end Mat_Zero_4x4

/////////////////////////////////////////////////////////////////////////////

void Mat_Copy_4x4(matrix_4x4 source, matrix_4x4 destination)
{
	// this function copies one 4x4 matrix to another

	int index_i,
		index_j; // looping vars

	// copy the matrix row by row

	for (index_i=0; index_i<4; index_i++)
		for (index_j=0; index_j<4; index_j++)
			destination[index_i][index_j] = source[index_i][index_j];

} // end Mat_Copy_4x4

/////////////////////////////////////////////////////////////////////////////

void Local_To_World_Object(object_ptr the_object)
{
	// this function converts an objects local coordinates to world coordinates
	// by translating each point in the object by the objects current position

	int index;  // looping variable

	// move object from local position to world position

	for (index=0; index<the_object->num_vertices; index++)
	{
		the_object->vertices_world[index].x = the_object->vertices_local[index].x +
											  the_object->world_pos.x;

		the_object->vertices_world[index].y = the_object->vertices_local[index].y +
											  the_object->world_pos.y;

		the_object->vertices_world[index].z = the_object->vertices_local[index].z +
											  the_object->world_pos.z;
    } // end for index

	// reset visibility flags for all polys

	for (index=0; index<the_object->num_polys; index++)
	{
		the_object->polys[index].visible = 1;
		the_object->polys[index].clipped = 0;
	} // end for

} // end Local_To_World_Object

/////////////////////////////////////////////////////////////////////////////

void Create_World_To_Camera(void)
{
	// this function creates the global inverse transformation matrix
	// used to transform world coordinate to camera coordinates

	matrix_4x4 translate,   // the translation matrix

			   rotate_x,    // the x,y and z rotation matrices
			   rotate_y,
			   rotate_z,
			   result_1,
			   result_2;

	// create identity matrices

	Mat_Identity_4x4(translate);
	Mat_Identity_4x4(rotate_x);
	Mat_Identity_4x4(rotate_y);
	Mat_Identity_4x4(rotate_z);

	// make a translation matrix based on the inverse of the viewpoint

	translate[3][0] = -view_point.x;
	translate[3][1] = -view_point.y;
	translate[3][2] = -view_point.z;

	// make rotation matrices based on the inverse of the view angles
	// note that since we use lookup tables for sin and cosine, it's hard to
	// use negative angles, so we will use that fact that cos(-x) = cos(x)
	// and sin(-x) = -sin(x) to implement the inverse instead of using
	// an offset in the lookup table or using the technique that
	// a rotation of -x = 360-x. note the original rotation formulas will be
	// kept in parentheses, so you can better see the inversion

	// x matrix

	rotate_x[1][1] =  ( cos_look[view_angle.ang_x]);
	rotate_x[1][2] = -( sin_look[view_angle.ang_x]);
	rotate_x[2][1] = -(-sin_look[view_angle.ang_x]);
	rotate_x[2][2] =  ( cos_look[view_angle.ang_x]);

	// y matrix

	rotate_y[0][0] =  ( cos_look[view_angle.ang_y]);
	rotate_y[0][2] = -(-sin_look[view_angle.ang_y]);
	rotate_y[2][0] = -( sin_look[view_angle.ang_y]);
	rotate_y[2][2] =  ( cos_look[view_angle.ang_y]);

	// z matrix

	rotate_z[0][0] =  ( cos_look[view_angle.ang_z]);
	rotate_z[0][1] = -( sin_look[view_angle.ang_z]);
	rotate_z[1][0] = -(-sin_look[view_angle.ang_z]);
	rotate_z[1][1] =  ( cos_look[view_angle.ang_z]);

	// multiply all the matrices together to obtain a final world to camera
	// viewing transformation matrix i.e.
	// translation * rotate_x * rotate_y * rotate_z

	Mat_Mul_4x4_4x4(translate,rotate_x,result_1);
	Mat_Mul_4x4_4x4(result_1,rotate_y,result_2);
	Mat_Mul_4x4_4x4(result_2,rotate_z,global_view);

} // end Create_World_To_Camera

/////////////////////////////////////////////////////////////////////////////

void World_To_Camera_Object(object_ptr the_object)
{
	// this function converts an objects world coordinates to camera coordinates
	// by multiplying each point of the object by the inverse viewing transformation
	// matrix which is generated by concatenating the inverse of the view position
	// and the view angles the result of which is in global_view

	int index; // looping variable

	// iterate thru all vertices of object and transform them into camera coordinates

	for (index=0; index<=the_object->num_vertices; index++)
	{
		// multiply the point by the viewing transformation matrix

		// x component
		the_object->vertices_camera[index].x =

                  the_object->vertices_world[index].x * global_view[0][0] +
                  the_object->vertices_world[index].y * global_view[1][0] +
                  the_object->vertices_world[index].z * global_view[2][0] +
                                                        global_view[3][0];

		// y component
		the_object->vertices_camera[index].y =

                  the_object->vertices_world[index].x * global_view[0][1] +
                  the_object->vertices_world[index].y * global_view[1][1] +
                  the_object->vertices_world[index].z * global_view[2][1] +
                                                        global_view[3][1];
		// z component
		the_object->vertices_camera[index].z =

                  the_object->vertices_world[index].x * global_view[0][2] +
                  the_object->vertices_world[index].y * global_view[1][2] +
                  the_object->vertices_world[index].z * global_view[2][2] +
                                                        global_view[3][2];

    } // end for index

} // end World_To_Camera_Object

////////////////////////////////////////////////////////////////////////////

void Rotate_Object(object_ptr the_object,int angle_x,int angle_y,int angle_z)
{
	// this function rotates an object relative to it's own local coordinate system
	// and allows simultaneous rotations

	int index,       //  looping variable
		product=0;   // used to determine which matrices need multiplying

	matrix_4x4 rotate_x,    // the x,y and z rotation matrices
			   rotate_y,
			   rotate_z,
			   rotate,      // the final rotation matrix
			   temp;        // temporary working matrix

	float temp_x, // used to hold intermediate results during rotation
		  temp_y,
		  temp_z;

	// test if we need to rotate at all
	if (angle_x==0 && angle_y==0 && angle_z==0)
		return;

	// create identity matrice
	Mat_Identity_4x4(rotate);

	// create X rotation matrix
	if (angle_x)
	{
		// x matrix
		Mat_Identity_4x4(rotate_x);

		rotate_x[1][1] = ( cos_look[angle_x]);
		rotate_x[1][2] = ( sin_look[angle_x]);
		rotate_x[2][1] = (-sin_look[angle_x]);
		rotate_x[2][2] = ( cos_look[angle_x]);

	} // end if any X rotation

	// create Y rotation matrix
	if (angle_y)
	{
		Mat_Identity_4x4(rotate_y);

		rotate_y[0][0] = ( cos_look[angle_y]);
		rotate_y[0][2] = (-sin_look[angle_y]);
		rotate_y[2][0] = ( sin_look[angle_y]);
		rotate_y[2][2] = ( cos_look[angle_y]);

	} // end if any Y rotation

	// create Z rotation matrix
	if (angle_z)
	{
		Mat_Identity_4x4(rotate_z);

		rotate_z[0][0] = ( cos_look[angle_z]);
		rotate_z[0][1] = ( sin_look[angle_z]);
		rotate_z[1][0] = (-sin_look[angle_z]);
		rotate_z[1][1] = ( cos_look[angle_z]);
	} // end if any Z rotation

	// compute final rotation matrix, determine the proper product of matrices
	// use a switch statement along with a bit pattern to determine which
	// matrices need multiplying, this is worth the time it would take
	// to concatenate matrices together that don't have any effect

	// if bit 2 of product is 1 then there is an x rotation
	// if bit 1 of product is 1 then there is an y rotation
	// if bit 0 of product is 1 then there is an z rotation

	if (angle_x)
		product|=4;

	if (angle_y)
		product|=2;

	if (angle_z)
		product|=1;

	// compute proper final rotation matrix
	switch(product)
	{
		case 0: // do nothing there isn't any rotation
		{
			// this shouldn't happen
		} break;

		case 1: // final matrix = z
		{
			Mat_Copy_4x4(rotate_z,rotate);
		} break;

		case 2: // final matrix = y
		{
			Mat_Copy_4x4(rotate_y,rotate);
		} break;

		case 3: // final matrix = y*z
		{
			Mat_Mul_4x4_4x4(rotate_y,rotate_z,rotate);
		} break;

		case 4: // final matrix = x
		{
			Mat_Copy_4x4(rotate_x,rotate);
		} break;

		case 5: // final matrix = x*z
		{
			Mat_Mul_4x4_4x4(rotate_x,rotate_z,rotate);
		} break;

		case 6: // final matrix = x*y
		{
			Mat_Mul_4x4_4x4(rotate_x,rotate_y,rotate);
		} break;

		case 7: // final matrix = x*y*z
		{
			Mat_Mul_4x4_4x4(rotate_x,rotate_y,temp);
			Mat_Mul_4x4_4x4(temp,rotate_z,rotate);
		} break;

		default:
			break;
	} // end switch

	// now multiply each point in object by transformation matrix

	for (index=0; index<the_object->num_vertices; index++)
	{
		// x component
		temp_x = the_object->vertices_local[index].x * rotate[0][0] +
				 the_object->vertices_local[index].y * rotate[1][0] +
				 the_object->vertices_local[index].z * rotate[2][0];

		// y component
		temp_y = the_object->vertices_local[index].x * rotate[0][1] +
				 the_object->vertices_local[index].y * rotate[1][1] +
				 the_object->vertices_local[index].z * rotate[2][1];

		// z component
		temp_z = the_object->vertices_local[index].x * rotate[0][2] +
				 the_object->vertices_local[index].y * rotate[1][2] +
				 the_object->vertices_local[index].z * rotate[2][2];

		// store rotated point back into local array
		the_object->vertices_local[index].x = temp_x;
		the_object->vertices_local[index].y = temp_y;
		the_object->vertices_local[index].z = temp_z;

    } // end for index

} // end Rotate_Object

////////////////////////////////////////////////////////////////////////////

void Position_Object(object_ptr the_object,int x,int y,int z)
{
	// this function positions an object in the world
	the_object->world_pos.x = x;
	the_object->world_pos.y = y;
	the_object->world_pos.z = z;

} // end Position_Object

////////////////////////////////////////////////////////////////////////////

void Translate_Object(object_ptr the_object,int x_trans,int y_trans,int z_trans)
{
	// this function translates an object relative to it's own local
	// coordinate system

	the_object->world_pos.x+=x_trans;
	the_object->world_pos.y+=y_trans;
	the_object->world_pos.z+=z_trans;

} // end Translate_Object

/////////////////////////////////////////////////////////////////////////////

void Scale_Object(object_ptr the_object,float scale_factor)
{
	// this function scales an object relative to it's own local coordinate system
	// equally in x,y and z

	int curr_poly,    // the current polygon being processed
		curr_vertex;  // the current vertex being processed

	float scale_2;    // holds the sqaure of the scaling factor, needed to
					  // resize the surface normal for lighting calculations

	// multiply each vertex in the object definition by the scaling factor
	for (curr_vertex=0; curr_vertex<the_object->num_vertices; curr_vertex++)
	{
		the_object->vertices_local[curr_vertex].x*=scale_factor;
		the_object->vertices_local[curr_vertex].y*=scale_factor;
		the_object->vertices_local[curr_vertex].z*=scale_factor;
    }

	// compute scaling factor squared
	scale_2 = scale_factor*scale_factor;

	// now scale all pre-computed normals
	for (curr_poly=0; curr_poly<the_object->num_polys; curr_poly++)
		the_object->polys[curr_poly].normal_length*=scale_2;

	// finally scale the radius up
	the_object->radius*=scale_factor;

} // end Scale_Object

/////////////////////////////////////////////////////////////////////////////

int Objects_Collide(object_ptr object_1, object_ptr object_2)
{
	// this function tests if the bounding spheres of two objects overlaps
	// if a more accurate test is needed then polygons should be tested against
	// polygons. note the function uses the fact that if x > y then x^2 > y^2
	// to avoid using square roots. Finally, the function might be altered
	// so that the bounding spheres are shrank to make sure that the collision
	// is "solid"/ finally, soft and hard collisions are both detected

	float dx,dy,dz,           // deltas in x,y and z
		  radius_1,radius_2,  // radi of each object
		  distance;           // distance between object centers

	// compute delta's
	dx = (object_1->world_pos.x - object_2->world_pos.x);
	dy = (object_1->world_pos.y - object_2->world_pos.y);
	dz = (object_1->world_pos.z - object_2->world_pos.z);

	// compute length
	distance = dx*dx + dy*dy + dz*dx;

	// compute radius of each object squared
	radius_1 = object_1->radius*object_1->radius;
	radius_2 = object_2->radius*object_2->radius;

	// test if distance is smaller than of radi
	if (distance<radius_1 || distance <radius_2)
		return(HARD_COLLISION); // hard collision
	else
	if (distance < radius_1+radius_2)
		return(SOFT_COLLISION); // soft collision
	else
		return(NO_COLLISION);

} // end Objects_Collide

/////////////////////////////////////////////////////////////////////////////

char *PLG_Get_Line(char *string, int max_length, FILE *fp)
{
	// this function gets a line from a PLG file and strips comments
	// just pretend it's a black box!

	char buffer[80]; // temporary string storage

	int length,      // length of line read
		index=0,     // looping variables
		index_2=0,
		parsed=0;    // has the current input line been parsed

	// get the next line of input, make sure there is something on the line
	while(1)
	{
		// get the line
		if (!fgets(buffer,max_length,fp))
			return(NULL);

		// get length of line
		length = strlen(buffer);

		// kill the carriage return
		buffer[length-1] = 0;

		// reset index
		index = 0;

		// eat leading white space
		while(buffer[index]==' ')
           index++;

		// read line into buffer, if "#" arrives in data stream then disregard
		// rest of line

		parsed=0;
		index_2=0;

		while(!parsed)
		{
			if (buffer[index]!='#' && buffer[index]!=';')
            {
				// insert character into output string
				string[index_2] = buffer[index];

				// test if this is a null terminator
				if (string[index_2]==0)
					parsed=1;

				// move to next character
				index++;
				index_2++;

			} // end if not comment
			else
            {
				// insert a null termination since this is the end of the
				// string for all intense purposes

				string[index_2] = 0;

				parsed=1;

			}

		} // end while parsing string

		// make sure we got a string and not a blank line
		if (strlen(string))
			return(string);

	} // end while

} // end PLG_Get_Line

/////////////////////////////////////////////////////////////////////////////

int PLG_Load_Object(object_ptr the_object,char *filename,float scale)
{
	// this function loads an object off disk and allows it to be scaled
	FILE *fp; // disk file

	static int id_number = 0; // used to set object id's

	char buffer[80],         // holds input string
		 object_name[32],    // name of 3-D object
		 *token;             // current parsing token

	unsigned int total_vertices,    // total vertices in object
				 total_polys,       // total polygons per object
				 num_vertices,      // number of vertices on a polygon
				 color_des,         // the color descriptor of a polygon
				 logical_color,     // the final color of polygon
				 shading,           // the type of shading used on polygon
				 index,             // looping variables
				 index_2,
				 vertex_num,        // vertex numbers
				 vertex_0,
				 vertex_1,
				 vertex_2;

	float x,y,z;                    // a single vertex

	vector_3d u,v,normal;           // working vectors

	// open the disk file
	if ((fp=fopen(filename,"r"))==NULL)
	{
		printf("\nCouldn't open file %s",filename);
		return(0);
	} // end if

	// first we are looking for the header line that has the object name and
	// the number of vertices and polygons
	if (!PLG_Get_Line(buffer, 80,fp))
	{
		printf("\nError with PLG file %s",filename);
		fclose(fp);
		return(0);
	} // end if error

	// extract object name and number of vertices and polygons
	sscanf(buffer,"%s %d %d",object_name, &total_vertices, &total_polys);

	// set proper fields in object
	the_object->num_vertices = total_vertices;
	the_object->num_polys    = total_polys;
	the_object->state        = 1;

	the_object->world_pos.x  = 0;
	the_object->world_pos.y  = 0;
	the_object->world_pos.z  = 0;

	// set id number, maybe later also add the name of object in the
	// structure???
	the_object->id = id_number++;

	// based on number of vertices, read vertex list into object
	for (index=0; index<total_vertices; index++)
	{
		// read in vertex
		if (!PLG_Get_Line(buffer, 80,fp))
		{
			printf("\nError with PLG file %s",filename);
			fclose(fp);
			return(0);
		} // end if error

		sscanf(buffer,"%f %f %f",&x,&y,&z);

		// insert vertex into object
		the_object->vertices_local[index].x = x*scale;
		the_object->vertices_local[index].y = y*scale;
		the_object->vertices_local[index].z = z*scale;

	} // end for index

	// now read in polygon list
	for (index=0; index<total_polys; index++)
	{
		// read in color and number of vertices for next polygon
		if (!PLG_Get_Line(buffer, 80,fp))
		{
			printf("\nError with PLG file %s",filename);
			fclose(fp);
			return(0);
		} // end if error

		// intialize token getter and get first token which is color descriptor
		if (!(token = strtok(buffer," ")) )
		{
			printf("\nError with PLG file %s",filename);
			fclose(fp);
			return(0);
		} // end if problem

		// test if number is hexadecimal
		if (token[0]=='0' && (token[1]=='x' || token[1]=='X'))
		{
			sscanf(&token[2],"%x",&color_des);
		} // end if hex color specifier
		else
		{
			color_des = atoi(token);
		} // end if decimal

		// extract base color and type of shading
		logical_color = color_des & 0x00ff;
		shading       = color_des >> 12;

		// read number of vertices in polygon
		if (!(token = strtok(NULL," ")))
		{
			printf("\nError with PLG file %s",filename);
			fclose(fp);
			return(0);
		} // end if problem

		if ((num_vertices = atoi(token))<=0)
		{
			printf("\nError with PLG file (number of vertices) %s",filename);
			fclose(fp);
			return(0);
		} // end if no vertices or error

		// set fields in polygon structure
		the_object->polys[index].num_points = num_vertices;
		the_object->polys[index].color      = logical_color;
		the_object->polys[index].shading    = shading;
		the_object->polys[index].two_sided  = 0;
		the_object->polys[index].visible    = 1;
		the_object->polys[index].clipped    = 0;
		the_object->polys[index].active     = 1;

		// now read in polygon vertice list
		for (index_2=0; index_2<num_vertices; index_2++)
        {
			// read in next vertex number

			if (!(token = strtok(NULL," ")) )
			{
				printf("\nError with PLG file %s",filename);
				fclose(fp);
				return(0);
			} // end if problem

			vertex_num = atoi(token);

			// insert vertex number into polygon
			the_object->polys[index].vertex_list[index_2] = vertex_num;

		} // end for index_2

		// compute length of the two co-planer edges of the polygon, since they
		// will be used in the computation of the dot-product later

		vertex_0 = the_object->polys[index].vertex_list[0];
		vertex_1 = the_object->polys[index].vertex_list[1];
		vertex_2 = the_object->polys[index].vertex_list[2];

		// the vector u = vo->v1
		Make_Vector_3D((point_3d_ptr)&the_object->vertices_local[vertex_0],
					   (point_3d_ptr)&the_object->vertices_local[vertex_1],
					   (vector_3d_ptr)&u);

		// the vector v = vo->v2
		Make_Vector_3D((point_3d_ptr)&the_object->vertices_local[vertex_0],
					   (point_3d_ptr)&the_object->vertices_local[vertex_2],
					   (vector_3d_ptr)&v);

		Cross_Product_3D((vector_3d_ptr)&v,
						 (vector_3d_ptr)&u,
						 (vector_3d_ptr)&normal);


		// compute magnitude of normal and store in polygon structure
		the_object->polys[index].normal_length = Vector_Mag_3D((vector_3d_ptr)&normal);

	} // end for index

	// close the file
	fclose(fp);

	// compute object radius
	Compute_Object_Radius(the_object);

	// return success
	return(1);

} // end PLG_Load_Object

///////////////////////////////////////////////////////////////////////////////

float Compute_Object_Radius(object_ptr the_object)
{
	// this function computes maximum radius of object, maybe a better method would
	// use average radius? Note that this functiopn shouldn't be used during
	// runtime but when an object is created

	float new_radius,   // used in average radius calculation of object
		  x,y,z;        // a single vertex

	int index;          // looping variable

	// reset object radius
	the_object->radius=0;

	for (index=0; index<the_object->num_vertices; index++)
	{
		x = the_object->vertices_local[index].x;
		y = the_object->vertices_local[index].y;
		z = the_object->vertices_local[index].z;

		// compute distance to point
		new_radius = (float)sqrt(x*x + y*y + z*z);

		// is this radius bigger than last?
		if (new_radius > the_object->radius)
			the_object->radius = new_radius;

    } // end for index

	// return radius just in case
	return(the_object->radius);

} // end Compute_Object_Radius

/////////////////////////////////////////////////////////////////////////////

void Clip_Object_3D(object_ptr the_object, int mode)
{
	// this function clips an object in camera coordinates against the 3D viewing
	// volume. the function has two modes of operation. In CLIP_Z_MODE the
	// function performs only a simple z extend clip with the near and far clipping
	// planes. In CLIP_XYZ_MODE mode the function performs a full 3-D clip

	int curr_poly;   // the current polygon being processed

	float x1,y1,z1,
		  x2,y2,z2,
		  x3,y3,z3,
		  x4,y4,z4,  // working variables used to hold vertices

		  x1_compare, // used to hold clipping points on x and y
		  y1_compare,
		  x2_compare,
		  y2_compare,
		  x3_compare,
		  y3_compare,
		  x4_compare,
		  y4_compare;

	// test if trivial z clipping is being requested
	if (mode==CLIP_Z_MODE)
	{
		// attempt to clip each polygon against viewing volume
		for (curr_poly=0; curr_poly<the_object->num_polys; curr_poly++)
		{
			// extract z components
			z1=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[0]].z;
			z2=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[1]].z;
			z3=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[2]].z;

			// test if this is a quad
			if (the_object->polys[curr_poly].num_points==4)
			{
				// extract 4th z component
				z4=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[3]].z;
			} // end if quad
			else
				z4=z3;

			// perform near and far z clipping test
			if ( (z1<clip_near_z && z2<clip_near_z && z3<clip_near_z && z4<clip_near_z) ||
				 (z1>clip_far_z && z2>clip_far_z && z3>clip_far_z && z4>clip_far_z) )
			{
				// set clipped flag
				the_object->polys[curr_poly].clipped=1;

			}
       }
    } // end if CLIP_Z_MODE
	else
	{
		// CLIP_XYZ_MODE, perform full 3D viewing volume clip
		for (curr_poly=0; curr_poly<the_object->num_polys; curr_poly++)
		{
			// extract x,y and z components
			x1=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[0]].x;
			y1=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[0]].y;
			z1=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[0]].z;

			x2=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[1]].x;
			y2=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[1]].y;
			z2=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[1]].z;

			x3=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[2]].x;
			y3=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[2]].y;
			z3=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[2]].z;

			// test if this is a quad
			if (the_object->polys[curr_poly].num_points==4)
			{
				// extract 4th vertex

				x4=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[3]].x;
				y4=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[3]].y;
				z4=the_object->vertices_camera[the_object->polys[curr_poly].vertex_list[3]].z;

				// do clipping tests

				// perform near and far z clipping test first

				if (!((z1>clip_near_z || z2>clip_near_z || z3>clip_near_z || z4>clip_near_z) &&
					  (z1<clip_far_z || z2<clip_far_z || z3<clip_far_z || z4<clip_far_z)) )
				{
					// set clipped flag

					the_object->polys[curr_poly].clipped=1;
					continue;
				}

				// pre-compute x comparision ranges

				x1_compare = (HALF_SCREEN_WIDTH*z1)/viewing_distance;
				x2_compare = (HALF_SCREEN_WIDTH*z2)/viewing_distance;
				x3_compare = (HALF_SCREEN_WIDTH*z3)/viewing_distance;
				x4_compare = (HALF_SCREEN_WIDTH*z4)/viewing_distance;

				// perform x test
				if (!((x1>-x1_compare || x2>-x1_compare || x3>-x3_compare || x4>-x4_compare) &&
					  (x1<x1_compare || x2<x2_compare || x3<x3_compare || x4<x4_compare))  )
				{
					// set clipped flag

					the_object->polys[curr_poly].clipped=1;
					continue;
				}

				// pre-compute x comparision ranges
				y1_compare = (HALF_SCREEN_HEIGHT*z1)/viewing_distance;
				y2_compare = (HALF_SCREEN_HEIGHT*z2)/viewing_distance;
				y3_compare = (HALF_SCREEN_HEIGHT*z3)/viewing_distance;
				y4_compare = (HALF_SCREEN_HEIGHT*z4)/viewing_distance;

				// perform x test

				if (!((y1>-y1_compare || y2>-y1_compare || y3>-y3_compare || y4>-y4_compare) &&
					  (y1<y1_compare || y2<y2_compare || y3<y3_compare || y4<y4_compare))  )
				{
					// set clipped flag
					the_object->polys[curr_poly].clipped=1;
					continue;
				}

			} // end if quad
			else
			{
				// must be triangle, perform clipping tests on only 3 vertices
				// do clipping tests

				// perform near and far z clipping test first
				if (!((z1>clip_near_z || z2>clip_near_z || z3>clip_near_z) &&
					  (z1<clip_far_z || z2<clip_far_z || z3<clip_far_z)) )
				{
					// set clipped flag
					the_object->polys[curr_poly].clipped=1;
					continue;
				} // end if clipped

				// pre-compute x comparision ranges
				x1_compare = (HALF_SCREEN_WIDTH*z1)/viewing_distance;
				x2_compare = (HALF_SCREEN_WIDTH*z2)/viewing_distance;
				x3_compare = (HALF_SCREEN_WIDTH*z3)/viewing_distance;

				// perform x test
				if (!((x1>-x1_compare || x2>-x1_compare || x3>-x3_compare ) &&
					  (x1<x1_compare || x2<x2_compare || x3<x3_compare ))  )
				{
					// set clipped flag
					the_object->polys[curr_poly].clipped=1;
					continue;
				}

				// pre-compute x comparision ranges
				y1_compare = (HALF_SCREEN_HEIGHT*z1)/viewing_distance;
				y2_compare = (HALF_SCREEN_HEIGHT*z2)/viewing_distance;
				y3_compare = (HALF_SCREEN_HEIGHT*z3)/viewing_distance;

				// perform x test
				if (!((y1>-y1_compare || y2>-y1_compare || y3>-y3_compare) &&
					  (y1<y1_compare || y2<y2_compare || y3<y3_compare ))  )
				{
					// set clipped flag
					the_object->polys[curr_poly].clipped=1;
					continue;
				}

			} // end else triangle

		} // end for curr_poly

	} // end else clip everything

} // end Clip_Object_3D

//////////////////////////////////////////////////////////////////////////////

void Remove_Backfaces_And_Shade(object_ptr the_object)
{
	// this function removes all the backfaces of an object by setting the removed
	// flag. This function assumes that the object has been transformed into
	// camera coordinates. Also, the function computes the flat shading of the
	// object

	int vertex_0,         // vertex indices
		vertex_1,
		vertex_2,
		curr_poly;        // current polygon

	float dp,             // the result of the dot product
		  intensity;      // the final intensity of the surface

	vector_3d u,v,           // general working vectors
			  normal,        // the normal to the surface begin processed
			  sight;         // line of sight vector

	// for each polygon in the object determine if it is pointing away from the
	// viewpoint and direction

	for (curr_poly=0; curr_poly<the_object->num_polys; curr_poly++)
	{
		// compute two vectors on polygon that have the same intial points
		vertex_0 = the_object->polys[curr_poly].vertex_list[0];
		vertex_1 = the_object->polys[curr_poly].vertex_list[1];
		vertex_2 = the_object->polys[curr_poly].vertex_list[2];

		// the vector u = vo->v1
		Make_Vector_3D((point_3d_ptr)&the_object->vertices_world[vertex_0],
					   (point_3d_ptr)&the_object->vertices_world[vertex_1],
					   (vector_3d_ptr)&u);

		// the vector v = vo-v2
		Make_Vector_3D((point_3d_ptr)&the_object->vertices_world[vertex_0],
					   (point_3d_ptr)&the_object->vertices_world[vertex_2],
					   (vector_3d_ptr)&v);

		// compute the normal to polygon v x u
		Cross_Product_3D((vector_3d_ptr)&v,
						 (vector_3d_ptr)&u,
						 (vector_3d_ptr)&normal);

		// compute the line of sight vector, since all coordinates are world all
		// object vertices are already relative to (0,0,0), thus
		sight.x = view_point.x-the_object->vertices_world[vertex_0].x;
		sight.y = view_point.y-the_object->vertices_world[vertex_0].y;
		sight.z = view_point.z-the_object->vertices_world[vertex_0].z;

		// compute the dot product between line of sight vector and normal to surface
		dp = Dot_Product_3D((vector_3d_ptr)&normal,(vector_3d_ptr)&sight);

		// set the clip flagged appropriately
		if (dp>0)
		{
			// set visibility
			the_object->polys[curr_poly].visible = 1;

			// compute light intensity if needed
			if (the_object->polys[curr_poly].shading==FLAT_SHADING)
			{
				// compute the dot product between the light source vector
				// and normal vector to surface
				dp = Dot_Product_3D((vector_3d_ptr)&normal,
									(vector_3d_ptr)&light_source);

				// test if light ray is reflecting off surface
				if (dp>0)
				{
					// now cos 0 = (u.v)/|u||v| or
					intensity = ambient_light + (15*dp/(the_object->polys[curr_poly].normal_length));

					// test if intensity has overflowed

					if (intensity >15)
						intensity = 15;

					// intensity now varies from 0-1, 0 being black or grazing and 1 being
					// totally illuminated. use the value to index into color table

					the_object->polys[curr_poly].shade =
						the_object->polys[curr_poly].color - (int)intensity;

				// printf("\nintensity of polygon %d is %f",curr_poly,intensity);

				} // end if light is reflecting off surface
				else
					the_object->polys[curr_poly].shade =
						the_object->polys[curr_poly].color - (int)ambient_light;

			} // end if use flat shading
			else
			{
				// assume constant shading and simply assign color to shade
				the_object->polys[curr_poly].shade = the_object->polys[curr_poly].color;
			} // end else constat shading

		} // end if face is visible
		else
			the_object->polys[curr_poly].visible = 0; // set invisible flag

#if DEBUG
		printf("\npolygon #%d",curr_poly);
		printf("\nu      = [%f,%f,%f]",u.x,u.y,u.z);
		printf("\nv      = [%f,%f,%f]",v.x,v.y,v.z);
		printf("\nnormal = [%f,%f,%f]",normal.x,normal.y,normal.z);
		printf("\nsight  = [%f,%f,%f]",sight.x,sight.y,sight.z);
		printf("\ndp     = %f\n",dp);
#endif

	} // end for curr_poly

} // end Remove_Backfaces_And_Shade

//////////////////////////////////////////////////////////////////////////////

int Remove_Object(object_ptr the_object, int mode)
{
	// this function determines if an entire object is within the viewing volume
	// or not by testing if the bounding sphere of the object in question
	// is within the viewing volume.In essence, this function "culls" entire objects

	float x_bsphere,   // the x,y and z components of the projected center of object
		  y_bsphere,
		  z_bsphere,
		  radius,     // the radius of object
		  x_compare,  // the extents of the clipping volume in x and y at the
		  y_compare;  // bounding spheres current z

	// first transform world position of object into camera coordinates

	// compute x component
	x_bsphere = the_object->world_pos.x * global_view[0][0] +
				the_object->world_pos.y * global_view[1][0] +
				the_object->world_pos.z * global_view[2][0] +
										  global_view[3][0];
	// compute y component
	y_bsphere = the_object->world_pos.x * global_view[0][1] +
				the_object->world_pos.y * global_view[1][1] +
				the_object->world_pos.z * global_view[2][1] +
										  global_view[3][1];
	// compute z component
	z_bsphere = the_object->world_pos.x * global_view[0][2] +
				the_object->world_pos.y * global_view[1][2] +
				the_object->world_pos.z * global_view[2][2] +
										  global_view[3][2];

	// extract radius of object
	radius = the_object->radius;
	if (mode==OBJECT_CULL_Z_MODE)
	{
		// first test against near and far z planes
		if ( ((z_bsphere-radius) > clip_far_z) ||
			 ((z_bsphere+radius) < clip_near_z) )
			return(1);
		else
			return(0);
	} // end if z only
	else
	{
		// perform full x,y,z test
		if ( ((z_bsphere-radius) > clip_far_z) ||
			 ((z_bsphere+radius) < clip_near_z) )
			return(1);

		// test against x right and left planes, first compute viewing volume
		// extents at position z position of bounding sphere
		x_compare = (HALF_SCREEN_WIDTH*z_bsphere)/viewing_distance;

		if ( ((x_bsphere-radius) > x_compare) ||
			 ((x_bsphere+radius) < -x_compare) )
			return(1);

		// finally test against y top and bottom planes
		y_compare = (INVERSE_ASPECT_RATIO*HALF_SCREEN_HEIGHT*z_bsphere)/viewing_distance;

		if ( ((y_bsphere-radius) > y_compare) ||
			 ((y_bsphere+radius) < -y_compare) )
			return(1);

		// else it just can't be removed!!!

		return(0);

	} // end else

} // end Remove_Object

//////////////////////////////////////////////////////////////////////////////

void Generate_Poly_List(object_ptr the_object,int mode)
{
	// this function is used to generate the final polygon list that will be
	// rendered. Object by object the list is built up

	int vertex,
		curr_vertex,
		curr_poly;

	// test if this is the first object to be inserted
	if (mode==RESET_POLY_LIST)
	{
		// reset number of polys to zero
		num_polys_frame=0;
		return;
	} // end if first

	// insert all visible polygons into polygon list
	for (curr_poly=0; curr_poly<the_object->num_polys; curr_poly++)
    {
		// test if this poly is visible, if so add it to poly list
		if (the_object->polys[curr_poly].visible &&
		   !the_object->polys[curr_poly].clipped)
		{
			// add this poly to poly list

			// first copy data and vertices into an open slot in storage area
			world_poly_storage[num_polys_frame].num_points = the_object->polys[curr_poly].num_points;
			world_poly_storage[num_polys_frame].color      = the_object->polys[curr_poly].color;
			world_poly_storage[num_polys_frame].shade      = the_object->polys[curr_poly].shade;
			world_poly_storage[num_polys_frame].shading    = the_object->polys[curr_poly].shading;
			world_poly_storage[num_polys_frame].two_sided  = the_object->polys[curr_poly].two_sided;
			world_poly_storage[num_polys_frame].visible    = the_object->polys[curr_poly].visible;
			world_poly_storage[num_polys_frame].clipped    = the_object->polys[curr_poly].clipped;
			world_poly_storage[num_polys_frame].active     = the_object->polys[curr_poly].active;

			// now copy vertices
			for (curr_vertex=0; curr_vertex<the_object->polys[curr_poly].num_points; curr_vertex++)
			{
				// extract vertex number
				vertex=the_object->polys[curr_poly].vertex_list[curr_vertex];

				// extract x,y and z
				world_poly_storage[num_polys_frame].vertex_list[curr_vertex].x
										= the_object->vertices_camera[vertex].x;

				world_poly_storage[num_polys_frame].vertex_list[curr_vertex].y
										= the_object->vertices_camera[vertex].y;

				world_poly_storage[num_polys_frame].vertex_list[curr_vertex].z
										= the_object->vertices_camera[vertex].z;
			} // end for curr_vertex

			// assign pointer to it
			world_polys[num_polys_frame] = &world_poly_storage[num_polys_frame];

			// increment number of polys
			num_polys_frame++;

		} // end if poly visible

    } // end for curr_poly

} // end Generate_Poly_List

//////////////////////////////////////////////////////////////////////////////

void Project_Polys(void)
{
	// this function performs the final 3-D to 2-D perspective projection of the
	// polygons

} // end Project_Polys

//////////////////////////////////////////////////////////////////////////////

void Draw_Line(int xo, int yo, int x1,int y1, unsigned char color,unsigned char far *vb_start)
{
	// this function draws a line from xo,yo to x1,y1 using differential error
	// terms (based on Bresenahams work)

	int dx,             // difference in x's
		dy,             // difference in y's
		x_inc,          // amount in pixel space to move during drawing
		y_inc,          // amount in pixel space to move during drawing
		error=0,        // the discriminant i.e. error i.e. decision variable
		index;          // used for looping

	// pre-compute first pixel address in video buffer

	vb_start = vb_start + ((unsigned int)yo<<6) +
						  ((unsigned int)yo<<8) +
						   (unsigned int)xo;

	// compute horizontal and vertical deltas
	dx = x1-xo;
	dy = y1-yo;

	// test which direction the line is going in i.e. slope angle
	if (dx>=0)
	{
		x_inc = 1;
	} // end if line is moving right
	else
	{
		x_inc = -1;
		dx    = -dx;  // need absolute value
	} // end else moving left

	// test y component of slope
	if (dy>=0)
	{
		y_inc = 320; // 320 bytes per line
	} // end if line is moving down
	else
	{
		y_inc = -320;
		dy    = -dy;  // need absolute value
	} // end else moving up

	// now based on which delta is greater we can draw the line
	if (dx>dy)
	{
		// draw the line
		for (index=0; index<=dx; index++)
		{
			// set the pixel
			*vb_start = color;

			// adjust the error term
			error+=dy;

			// test if error has overflowed
			if (error>dx)
			{
				error-=dx;

				// move to next line
				vb_start+=y_inc;
			} // end if error overflowed

			// move to the next pixel
			vb_start+=x_inc;
		} // end for
	} // end if |slope| <= 1
	else
	{
		// draw the line
		for (index=0; index<=dy; index++)
		{
			// set the pixel
			*vb_start = color;

			// adjust the error term
			error+=dx;

			// test if error overflowed
			if (error>0)
			{
				error-=dy;

				// move to next line
				vb_start+=x_inc;
			} // end if error overflowed

		// move to the next pixel
		vb_start+=y_inc;

		} // end for
	} // end else |slope| > 1

} // end Draw_Line

///////////////////////////////////////////////////////////////////////////////

void Draw_Object_Wire(object_ptr the_object)
{
	// this function draws an object out of wires

	int curr_poly,      // the current polygon
		curr_vertex,    // the current vertex
		vertex;         // vertex index

	float x1,y1,z1,     // working variables
		  x2,y2,z2;

	int ix1,iy1,        // integers used to hold screen coordinates
		ix2,iy2;

	// compute position of object in world

	for (curr_poly=0; curr_poly<the_object->num_polys; curr_poly++)
	{
		// is this polygon visible?

		if (the_object->polys[curr_poly].visible==0 ||
			the_object->polys[curr_poly].clipped )
			continue;

		// printf("\npolygon #%d",curr_poly);

		for (curr_vertex=0; curr_vertex<the_object->polys[curr_poly].num_points-1; curr_vertex++)
		{
			// extract two endpoints
			vertex=the_object->polys[curr_poly].vertex_list[curr_vertex];

			x1 = the_object->vertices_camera[vertex].x;
			y1 = the_object->vertices_camera[vertex].y;
			z1 = the_object->vertices_camera[vertex].z;

			vertex=the_object->polys[curr_poly].vertex_list[curr_vertex+1];

			x2 = the_object->vertices_camera[vertex].x;
			y2 = the_object->vertices_camera[vertex].y;
			z2 = the_object->vertices_camera[vertex].z;

			// convert to screen ccordinates
			x1=(HALF_SCREEN_WIDTH  + x1*viewing_distance/z1);
			y1=(HALF_SCREEN_HEIGHT - ASPECT_RATIO*y1*viewing_distance/z1);

			x2=(HALF_SCREEN_WIDTH  + x2*viewing_distance/z2);
			y2=(HALF_SCREEN_HEIGHT - ASPECT_RATIO*y2*viewing_distance/z2);

			// convert floats to integers for line clipper
			ix1=(int)x1;
			iy1=(int)y1;
			ix2=(int)x2;
			iy2=(int)y2;

			// draw clipped lines
			if (Clip_Line(&ix1,&iy1,&ix2,&iy2))
			{
				Draw_Line((int)ix1,(int)iy1,(int)ix2,(int)iy2,
						  (unsigned char)the_object->polys[curr_poly].color,
						   double_buffer);
			} // end if clip

		} // end for vertex

		// close polygon
		ix1=(int)x2;
		iy1=(int)y2;

		// extract starting point again to close polygon
		vertex=the_object->polys[curr_poly].vertex_list[0];

		x2 = the_object->vertices_camera[vertex].x;
		y2 = the_object->vertices_camera[vertex].y;
		z2 = the_object->vertices_camera[vertex].z;

		// compute screen coordinates
		x2=(HALF_SCREEN_WIDTH  + x2*viewing_distance/z2);
		y2=(HALF_SCREEN_HEIGHT - ASPECT_RATIO*y2*viewing_distance/z2);

		// convert floats to integers
		ix2=(int)x2;
		iy2=(int)y2;

		// draw clipped lines
		if (Clip_Line(&ix1,&iy1,&ix2,&iy2))
		{
			Draw_Line((int)ix1,(int)iy1,(int)ix2,(int)iy2,
					  (unsigned char)the_object->polys[curr_poly].color,
					   double_buffer);
		} // end if clip

    } // end for curr_poly

} // end Draw_Object_Wire

///////////////////////////////////////////////////////////////////////////////

void Draw_Object_Solid(object_ptr the_object)
{
	// this function draws an object shaded solid and can perform
	// simple z entent clipping

	int curr_poly,      // the current polygon
		vertex_1,       // vertex index numbers
		vertex_2,
		vertex_3,
		vertex_4,
		is_quad=0;      // quadrilateral flag

	float x1,y1,z1,     // working variables
		  x2,y2,z2,
		  x3,y3,z3,
		  x4,y4,z4;

	// compute position of object in world
	for (curr_poly=0; curr_poly<the_object->num_polys; curr_poly++)
	{
		// is this polygon visible?

		if (the_object->polys[curr_poly].visible==0 ||
			the_object->polys[curr_poly].clipped )
			continue;

		// extract the vertex numbers
		vertex_1=the_object->polys[curr_poly].vertex_list[0];
		vertex_2=the_object->polys[curr_poly].vertex_list[1];
		vertex_3=the_object->polys[curr_poly].vertex_list[2];

		// do Z clipping first before projection
		z1=the_object->vertices_camera[vertex_1].z;
		z2=the_object->vertices_camera[vertex_2].z;
		z3=the_object->vertices_camera[vertex_3].z;

		// test if this is a quad
		if (the_object->polys[curr_poly].num_points==4)
		{
			// extract vertex number and z component for clipping and projection
			vertex_4=the_object->polys[curr_poly].vertex_list[3];
			z4=the_object->vertices_camera[vertex_4].z;

			// set quad flag
			is_quad=1;

		} // end if quad
		else
			z4=z3;

#if 0
		// perform z clipping test
		if ( (z1<clip_near_z && z2<clip_near_z && z3<clip_near_z && z4<clip_near_z) ||
			 (z1>clip_far_z && z2>clip_far_z && z3>clip_far_z && z4>clip_far_z) )
			continue;
#endif

		// extract points of polygon
        x1 = the_object->vertices_camera[vertex_1].x;
        y1 = the_object->vertices_camera[vertex_1].y;

        x2 = the_object->vertices_camera[vertex_2].x;
        y2 = the_object->vertices_camera[vertex_2].y;

        x3 = the_object->vertices_camera[vertex_3].x;
        y3 = the_object->vertices_camera[vertex_3].y;

        // compute screen position of points
        x1=(HALF_SCREEN_WIDTH  + x1*viewing_distance/z1);
        y1=(HALF_SCREEN_HEIGHT - ASPECT_RATIO*y1*viewing_distance/z1);

        x2=(HALF_SCREEN_WIDTH  + x2*viewing_distance/z2);
        y2=(HALF_SCREEN_HEIGHT - ASPECT_RATIO*y2*viewing_distance/z2);

        x3=(HALF_SCREEN_WIDTH  + x3*viewing_distance/z3);
        y3=(HALF_SCREEN_HEIGHT - ASPECT_RATIO*y3*viewing_distance/z3);

        // draw triangle
        Draw_Triangle_2D((int)x1,(int)y1,(int)x2,(int)y2,(int)x3,(int)y3,
                         the_object->polys[curr_poly].shade);

        // draw second poly if this is a quad
        if (is_quad)
		{
			// extract the point
			x4 = the_object->vertices_camera[vertex_4].x;
			y4 = the_object->vertices_camera[vertex_4].y;

			// poject to screen
			x4=(HALF_SCREEN_WIDTH  + x4*viewing_distance/z4);
			y4=(HALF_SCREEN_HEIGHT - ASPECT_RATIO*y4*viewing_distance/z4);

			// draw triangle
			Draw_Triangle_2D((int)x1,(int)y1,(int)x3,(int)y3,(int)x4,(int)y4,
							  the_object->polys[curr_poly].shade);
		} // end if quad

	} // end for curr_poly

} // end Draw_Object_Solid

///////////////////////////////////////////////////////////////////////////////

void Draw_Poly_List(void)
{
	// this function draws the global polygon list generated by calls to
	// Generate_Poly_List

	int curr_poly,          // the current polygon
		is_quad=0;          // quadrilateral flag

	float x1,y1,z1,         // working variables
		  x2,y2,z2,
		  x3,y3,z3,
		  x4,y4,z4;

	// draw each polygon in list
	for (curr_poly=0; curr_poly<num_polys_frame; curr_poly++)
	{
		// reset quad flag
		is_quad=0;

		// do Z clipping first before projection
		z1=world_polys[curr_poly]->vertex_list[0].z;
		z2=world_polys[curr_poly]->vertex_list[1].z;
		z3=world_polys[curr_poly]->vertex_list[2].z;

		// test if this is a quad
		if (world_polys[curr_poly]->num_points==4)
		{
			// extract vertex number and z component for clipping and projection
			z4=world_polys[curr_poly]->vertex_list[3].z;

			// set quad flag
			is_quad=1;

		} // end if quad
		else
			z4=z3;

#if 0
		// perform z clipping test
		if ( (z1<clip_near_z && z2<clip_near_z && z3<clip_near_z && z4<clip_near_z) ||
		     (z1>clip_far_z && z2>clip_far_z && z3>clip_far_z && z4>clip_far_z) )
			continue;
#endif

        // extract points of polygon
        x1 = world_polys[curr_poly]->vertex_list[0].x;
        y1 = world_polys[curr_poly]->vertex_list[0].y;

        x2 = world_polys[curr_poly]->vertex_list[1].x;
        y2 = world_polys[curr_poly]->vertex_list[1].y;

        x3 = world_polys[curr_poly]->vertex_list[2].x;
        y3 = world_polys[curr_poly]->vertex_list[2].y;


        // compute screen position of points

        x1=(HALF_SCREEN_WIDTH  + x1*viewing_distance/z1);
        y1=(HALF_SCREEN_HEIGHT - ASPECT_RATIO*y1*viewing_distance/z1);

        x2=(HALF_SCREEN_WIDTH  + x2*viewing_distance/z2);
        y2=(HALF_SCREEN_HEIGHT - ASPECT_RATIO*y2*viewing_distance/z2);

        x3=(HALF_SCREEN_WIDTH  + x3*viewing_distance/z3);
        y3=(HALF_SCREEN_HEIGHT - ASPECT_RATIO*y3*viewing_distance/z3);

        // draw triangle

        Draw_Triangle_2D((int)x1,(int)y1,(int)x2,(int)y2,(int)x3,(int)y3,
                         world_polys[curr_poly]->shade);


        // draw second poly if this is a quad

        if (is_quad)
           {
           // extract the point

           x4 = world_polys[curr_poly]->vertex_list[3].x;
           y4 = world_polys[curr_poly]->vertex_list[3].y;

           // poject to screen

           x4=(HALF_SCREEN_WIDTH  + x4*viewing_distance/z4);
           y4=(HALF_SCREEN_HEIGHT - ASPECT_RATIO*y4*viewing_distance/z4);

           // draw triangle

           Draw_Triangle_2D((int)x1,(int)y1,(int)x3,(int)y3,(int)x4,(int)y4,
                            world_polys[curr_poly]->shade);

           } // end if quad

    } // end for curr_poly

} // end Draw_Poly_List

//////////////////////////////////////////////////////////////////////////////

int Poly_Compare(facet **arg1, facet **arg2)
{
	// this function comapares the average z's of two polygons and is used by the
	// depth sort surface ordering algorithm

	float z1,z2;
	facet_ptr poly_1,poly_2;

	// dereference the poly pointers
	poly_1 = (facet_ptr)*arg1;
	poly_2 = (facet_ptr)*arg2;

	// compute z average of each polygon
	if (poly_1->num_points==3)
	{
		// compute average of 3 point polygon
		z1 = (float)0.33333*(poly_1->vertex_list[0].z+
							 poly_1->vertex_list[1].z+
							 poly_1->vertex_list[2].z);
	}
	else
	{
		// compute average of 4 point polygon
		z1 = (float)0.25*(poly_1->vertex_list[0].z+
						  poly_1->vertex_list[1].z+
						  poly_1->vertex_list[2].z+
						  poly_1->vertex_list[3].z);

	} // end else

	// now polygon 2
	if (poly_2->num_points==3)
	{
		// compute average of 3 point polygon
		z2 =(float)0.33333*(poly_2->vertex_list[0].z+
							poly_2->vertex_list[1].z+
							poly_2->vertex_list[2].z);
	}
	else
	{
		// compute average of 4 point polygon
		z2 = (float)0.25*(poly_2->vertex_list[0].z+
						  poly_2->vertex_list[1].z+
						  poly_2->vertex_list[2].z+
						  poly_2->vertex_list[3].z);
	} // end else

	// compare z1 and z2, such that polys' will be sorted in descending Z order

	if (z1>z2)
		return(-1);
	else if (z1<z2)
		return(1);
	else
		return(0);

} // end Poly_Compare

/////////////////////////////////////////////////////////////////////////////

void Print_Poly_List(void)
{
	// diagnostic function used to print out polygon list average z's
	int index;
	float z;

	printf("\npoly list z values");

	for (index=0; index<num_polys_frame; index++)
	{

		if (world_polys[index]->num_points==3)
		{
			z = (float)0.33333*(world_polys[index]->vertex_list[0].z+
								world_polys[index]->vertex_list[1].z+
								world_polys[index]->vertex_list[2].z);
		}
		else
		{
			z = (float)0.25*(world_polys[index]->vertex_list[0].z+
							 world_polys[index]->vertex_list[1].z+
							 world_polys[index]->vertex_list[2].z+
							 world_polys[index]->vertex_list[3].z);
		} // end else

		printf("\nz = %f",z);

    } // end for index

} // end Print_Poly_List

//////////////////////////////////////////////////////////////////////////////

void Sort_Poly_List(void)
{
	// this function does a simple z sort on the poly list to order surfaces
	// the list is sorted in descending order, i.e. farther polygons first

	qsort((void *)world_polys,num_polys_frame, sizeof(facet_ptr), Poly_Compare);

} // end Sort_Poly_List

///////////////////////////////////////////////////////////////////////////////

int Load_Palette_Disk(char *filename, RGB_palette_ptr the_palette)
{
	// this function loads a color palette from disk
	int index; // used for looping
	RGB_color color;
	FILE *fp;

	// open the disk file
	if (!(fp = fopen(filename,"r")))
		return(0);

	// load in all the colors
	for (index=0; index<=255; index++)
	{
		// get the next color
		fscanf(fp,"%d %d %d",&color.red,&color.green,&color.blue);

		// store the color in next element of palette
		the_palette->colors[index].red   = color.red;
		the_palette->colors[index].green = color.green;
		the_palette->colors[index].blue  = color.blue;

    } // end for index

	// set palette size to a full palette
	the_palette->start_reg = 0;
	the_palette->end_reg   = 255;

	// close the file and return success
	fclose(fp);

	return(1);

} // end Load_Palette_Disk

///////////////////////////////////////////////////////////////////////////////

int Save_Palette_Disk(char *filename, RGB_palette_ptr the_palette)
{
	// this function saves a palette to disk
	int index; // used for looping
	RGB_color color;
	FILE *fp;

	// open the disk file
	if (!(fp = fopen(filename,"w")))
		return(0);

	// write 255 lines of r g b
	for (index=0; index<=255; index++)
    {
		// get the next color
		// store the color in next element of palette
		color.red   = the_palette->colors[index].red;
		color.green = the_palette->colors[index].green;
		color.blue  = the_palette->colors[index].blue;

		// write the color to disk file
		fprintf(fp,"\n%d %d %d",color.red, color.green, color.blue);
    } // end for index

	// close the file and return success
	fclose(fp);

	return(1);

} // end Save_Palette_Disk


